import React, { useEffect } from 'react';
import { connect } from 'dva';
import { ConfigProvider } from 'antd';
import find from 'lodash/find';
import router from 'umi/router';
import { getLocale } from 'umi-plugin-react/locale';
import memoizeOne from 'memoize-one';
import isEqual from 'lodash/isEqual';
import isPlainObject from 'lodash/isPlainObject';
import dynamic from 'umi/dynamic';
import enUS from 'antd/es/locale/en_US';
import zhCN from 'antd/es/locale/zh_CN';
import moment from 'moment';
import { Redirect } from 'umi';
import pathToRegexp from 'path-to-regexp';
import { stringify } from 'qs';
import 'moment/locale/zh-cn';
import traceLogAction from '@/services/traceLogAction';
import mapping, { checkStatus } from '@/services/login';
import { getItem, setItem, parseQuery } from '@/utils/utils';
import LoadingComponent from '@/components/PageLoading/index';
import Locked from '@/pages/User/Locked';
import { themeConfig, enableWatermark, loginRedirect, visitor } from '@/defaultSettings';
import watermark from '@/utils/waterMark';

let locale = zhCN;

if (getLocale() === 'zh-CN') {
  locale = zhCN;
  moment.locale('zh-cn');
}

if (getLocale() === 'en-US') {
  locale = enUS;
  moment.locale('en-US');
}

const BasicLayout = dynamic({
  loader: () => import('./BasicLayout'),
  loading: LoadingComponent,
});

const UserLayout = dynamic({
  loader: () => import('./UserLayout'),
  loading: LoadingComponent,
});

let statusIntervalId = null; // checkStatus轮询

/**
 * 以pathname从后端返回的菜单数组中提取当前页面的菜单信息,情况分3种
 * 1、pathname以iframe开头
 * 2、普通路由
 * 3、嵌套路由 如：/user/login; /iframe/2769963
 * @param {string} pathname 当前页面的pathname,
 * @param {object[]} all 后端返回的菜单数组
 * @param {object[]} route
 * @returns {Object|undefined}  如果是对象，则必须包含3个字段：{menuId,urlAddr,menuName,menuCode}
 */
function _findMenu(pathname, all, route) {
  let result;
  // 1、形式如/iframe/2769963的表示都是第三方提供的内嵌页面，用menuId查找
  if (/^\/iframe\/\w+/i.test(pathname)) {
    const menuCode = pathname.match(/^\/iframe\/(\w+)/)[1];
    result = find(all, { menuCode });
  } else {
    const menu = find(all, { urlAddr: pathname });
    if (isPlainObject(menu)) {
      // 2、普通路由
      result = menu;
    } else if (menu === undefined) {
      // 3、嵌套路由
      const parent = find(all, item => {
        const { urlAddr } = item;
        // 有的菜单没有配置urlAddr
        if (urlAddr === undefined) {
          return false;
        }
        const rule = urlAddr.endsWith('/') ? `${urlAddr}:id?` : `${urlAddr}/:id?`;
        // 不区分大小写  pathToRegexp('/notice/:id?').test('/notice/123')  => true
        return pathToRegexp(rule).test(pathname);
      });
      if (parent === undefined) {
        // 嵌套路由 不匹配
        result = undefined;
      } else if (isPlainObject(parent)) {
        const component = find(route, item => {
          // 匹配 /notice/123 这样的动态路由
          const { path } = item;

          // 有的菜单没有配置urlAddr
          if (path === undefined) {
            return false;
          }
          const rule = path.endsWith('/') ? `${path}:id?` : `${path}/:id?`;
          //
          return pathToRegexp(rule).test(pathname);
        });
        // 嵌套路由 匹配
        if (isPlainObject(component)) {
          const { menuId, urlAddr, menuName, menuCode } = parent;
          result = { menuId, urlAddr, menuName, menuCode };
        } else {
          result = undefined;
        }
      }
    }
  }
  return result;
}

const findMenu = memoizeOne(_findMenu, isEqual);

function FrameLayout(props) {
  const {
    status,
    children,
    dispatch,
    initDataConfirmed,
    all,
    success,
    user,
    viewMode,
    ...rest
  } = props;
  const {
    location: { pathname },
    route,
  } = rest;

  /**
   * 这块的处理逻辑 需要兼顾noraml和inner两种视图模式
   *
   * ## normal模式
   * 1、未登录，登陆状态超时 或 访问/user/login
   *  - 使用 UserLayout模板
   * 2、已登录
   *  - 访问异常页面 则使用 Exception模板
   *  - 初始化失败：获取BasicLayout页面初始化数据失败，跳转到500页面
   *  - 初始化成功
   *    - 访问页面不存在 则使用 Exception模板(因为判断页面是否存在依赖于异步数据all，所以在异步结束前返回null，即让页面空白)
   *    - 其余使用 BasicLayout (进入basicLayout页面之前，需要保证初始化data异步获取成功)
   *
   * ## inner模式
   * 1. 未登录
   *  - 始总停留在loading界面，等待sessionId校验。
   * 2. 状态过期或直接访问登录页
   *  - redirect到403页面
   * 2. 已登录
   *  - 处理逻辑同normal模式下的已登录
   */
  function renderLayout() {
    // 未登录 - viewMode=inner
    if (viewMode === 'inner' && status === mapping.LOG_OUT) {
      return <LoadingComponent />;
    }
    if (
      viewMode === 'inner' &&
      (status === mapping.SESSION_TIME_OUT || pathname.toLocaleLowerCase() === '/user/login')
    ) {
      return <Redirect to="/403" />;
    }
    // 未登录
    if (
      viewMode === 'normal' &&
      (status === mapping.LOG_OUT ||
        status === mapping.SESSION_TIME_OUT ||
        pathname.toLocaleLowerCase() === '/user/login')
    ) {
      return <UserLayout {...rest}>{children}</UserLayout>;
    }

    if (viewMode === 'normal' && status === mapping.LOCKED) {
      return <Locked />;
    }

    // 已登录，直接访问异常页面
    if (/^\/((404)|(500)|(403)\/?)$/.test(pathname)) {
      const Exception = dynamic({
        loader: () => import(`@/pages/Exception/${pathname.split('/')[1]}`),
        loading: LoadingComponent,
      });
      return <Exception />;
    }

    // 已登录，异步未发起或异步进行中
    if (initDataConfirmed === false) {
      return <LoadingComponent />;
    }

    // 初始化失败：获取basicLayout初始化数据异常
    if (all.length === 0) {
      return <Redirect to="/500" />;
    }

    // 初始化成功：没找到当前页数据（首页是个特例 需要剔除）
    const currMenu = findMenu(pathname, all, route);
    if (pathname !== '/' && currMenu === undefined) {
      return <Redirect to="/404" />;
    }

    // 添加水印
    // TODO: 需要在退出时清掉水印
    if (!document.getElementById('wm_div_id') && user && enableWatermark) {
      const {
        userInfo: { userName },
      } = user;

      watermark.init({
        watermark_id: 'wm_div_id', // 水印总体的id
        watermark_prefix: 'mask_div_id', // 小水印的id前缀
        watermark_txt: `火麒麟 政企智慧中台 ${userName}`, // 水印的内容
        watermark_x: -100, // 水印起始位置x轴坐标
        watermark_y: 30, // 水印起始位置Y轴坐标
        watermark_rows: 0, // 水印行数
        watermark_cols: 0, // 水印列数
        watermark_x_space: 100, // 水印x轴间隔
        watermark_y_space: 50, // 水印y轴间隔
        watermark_font: '微软雅黑', // 水印字体
        watermark_color: 'black', // 水印字体颜色
        watermark_fontsize: '13px', // 水印字体大小
        watermark_alpha: 0.06, // 水印透明度，要求设置在大于等于0.005
        watermark_width: 130, // 水印宽度
        watermark_height: 100, // 水印长度
        watermark_angle: 15, // 水印倾斜度数
        watermark_parent_width: 0, // 水印的总体宽度（默认值：body的scrollWidth和clientWidth的较大值）
        watermark_parent_height: 0, // 水印的总体高度（默认值：body的scrollHeight和clientHeight的较大值）
        watermark_parent_node: null, // 水印插件挂载的父元素element,不输入则默认挂在body上
      });
    }

    // currMenu {object} 当前菜单信息 必须包含3个字段：{menuId,urlAddr,menuName}
    return (
      <BasicLayout
        {...rest}
        currMenu={
          pathname === '/' ? { menuName: '首页', urlAddr: '/', menuId: undefined } : currMenu
        }
      >
        {children}
      </BasicLayout>
    );
  }

  useEffect(() => {
    if (visitor !== true) {
      dispatch({
        type: 'login/checkLogin',
      });
    }
  }, [dispatch]);

  /**
   * 监听登录状态
   * 1、登录成功后： a、请求菜单数据；b、监听并记录用户最后操作时间；c、开始轮询，登录状态是否超时
   * 2、其他状态，停止以上a\b\c
   */
  useEffect(() => {
    if (status === mapping.LOGINED) {
      dispatch({
        type: 'menu/getInitData',
      });
      // 开始监听用户动作，并记录最后操作时间
      traceLogAction.start();

      if (statusIntervalId === null && visitor !== true) {
        // 每隔10秒状态检查一下，看看是否被踢下线了
        // 心跳时间修改为60秒
        statusIntervalId = window.setInterval(function() {
          checkStatus(dispatch, getItem('lastOpDate'));
        }, 1000 * mapping.POLLING_INTERVAL);
      }
    } else {
      traceLogAction.stop();
      clearInterval(statusIntervalId);
      statusIntervalId = null;
    }
  }, [dispatch, status]);

  // useEffect(() => {
  //   renderLayout(props);
  // });

  /**
   * 负责**未登录**时，页面的跳转。不论访问什么页面统一跳转到/user/login。
   * 并且需要带上'?redirtct=xxx'。
   * 比如:访问http://localhost:8000/#/bulletin
   * 需要变成'http://localhost:/user/login?redirtct=http://localhost:8000/#/bulletin'
   * 用于登录成功后跳转至指定页面。
   * 同时需要判断是否已带有'?redirtct=xxx' 因为登出时会自动在url上添加。避免重复
   */
  useEffect(() => {
    if (viewMode === 'normal' && status === mapping.LOG_OUT && pathname !== '/user/login') {
      if (
        (/^\/user*/.test(pathname) || pathname.indexOf('/500') !== -1) &&
        window.location.href.indexOf('?redirect=http') === -1
      ) {
        // 登录页相关，且不带'?redirtct=xxx'。
        router.push({
          pathname: '/user/login',
        });
      } else if (
        !/^\/user*/.test(pathname) &&
        window.location.href.indexOf('user/login?redirect=http') === -1
      ) {
        // 内页且不包含'?redirtct=xxx'
        let obj = { pathname: '/user/login' };
        // 重定向开关
        if (loginRedirect === true) {
          obj = {
            ...obj,
            search: stringify({
              redirect: window.location.href,
            }),
          };
        }
        router.push(obj);
      }
    }
  }, [pathname, status, viewMode]);

  useEffect(() => {
    const { bss3SessionId } = parseQuery(window.location.href);
    if (viewMode === 'inner' && bss3SessionId) {
      // 设置bss3SessionId
      setItem('bss3SessionId', bss3SessionId);
      // TODO: 需要更换单点登录接口服务的地址，目前的服务是Mock的,所以需要先用user/login正常登录一边，再切换到inner模式时 否则在获取菜单数据时可能会出现500错误
      // dispatch({
      //   type: 'login/ssoLogin',
      //   payload: { bss3SessionId },
      // });
    }
  }, [viewMode, dispatch, status]);

  return (
    <ConfigProvider locale={locale} prefixCls={themeConfig['@ant-prefix']}>
      {renderLayout()}
    </ConfigProvider>
  );
}

export default connect(({ login, menu, setting }) => ({
  status: login.status,
  viewMode: setting.viewMode,
  user: login.user,
  all: menu.all,
  success: menu.success,
  initDataConfirmed: menu.initDataConfirmed,
}))(FrameLayout);
